/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.core.utils
{
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.xml.XMLNode;
	
	import org.etherframework.core.reflection.Parameter;
	
	public final class ClassUtils
	{
		private static const _cache:Dictionary = new Dictionary();
		
		public static function getClass(obj:Object):Object
		{
			try {
				return (obj is String) ? getDefinitionByName(obj as String) : obj.constructor;
			}catch (e:*){}
			
			return null;
		}
		
		// TODO: Improve.
		public static function implementsInterface(value:Object, implementz:Class):Boolean
		{
			if (value is Class)
			{
				var ctype:ClassType = describeClassType(value);
				if (!ctype.interfaces) processInterfaces(value, ctype);
				
				return ctype.interfaces[implementz];
			}
			
			return (value is implementz);
		}
		
		// TODO: Improve.
		public static function extendsOrImplementsType(value:Object, type:Class):Boolean
		{
			if (value is Class)
			{
				const ctype:ClassType = describeClassType(value);
				
				if (!ctype.interfaces) processInterfaces(value, ctype);
				if (ctype.interfaces[type])
					return true;
				
				if (!ctype.ancestors) processAncestors(value, ctype);
				return ctype.ancestors[type] as Boolean;
			}
			
			return (value is type);
		}
		
		// TODO: Refactor.
		public static function getConstructorArgs(value:Class):Vector.<Parameter>
		{
			const classType:ClassType = describeClassType(value);
			
			if (classType.constructorArgs)
				return classType.constructorArgs;
			
			const member:XML = classType.descriptor.factory.constructor[0];
			
			if (!member)
				return null;
			
			var result:Vector.<Parameter>;
			var items:XMLList = member.parameter;
			var typeName:String;
			var type:Object;
			
			for each(var item:XML in items) 
			{
				if (!result)
					result = new Vector.<Parameter>();
				
				typeName = item.@type;
				
				switch (typeName)
				{
					case "void":
					case "*":
					{
						type = typeName;
						break;
					}
					default:
					{
						type = getDefinitionByName(typeName);
					}
				}
				
				result.push(new Parameter(item.@index, type, 
					(item.@optional == "true")));
			}
			
			if (result)
				result.fixed = true;
			
			classType.constructorArgs = result;
			
			return result;
		}
		
		// TODO: Refactor.
		public static function describeClassMembers(value:Object):Dictionary
		{
			const type:ClassType = describeClassType(value);
			
			if (type.members)
				return type.members;
			
			const result:Dictionary = new Dictionary();
			var currentType:ClassType;
			var typeNodes:XMLList = type.descriptor.factory.implementsInterface.@type;
			var typeNodesLen:int = typeNodes.length();
			var memberNodes:XMLList;
			var memberNode:String;
			var i:int;
			
			for (i = typeNodesLen - 1; i > -1 ; i--) 
			{
				currentType = describeClassType(String(typeNodes[i]));
				result[currentType.reference] = true;
				memberNodes = currentType.descriptor.factory.accessor.(@access == "readwrite").@name;
				
				for each (memberNode in memberNodes) 
				{
					if (!result[memberNode])
						result[memberNode] = currentType;
				}
				
				memberNodes = currentType.descriptor.factory.method.@name;
				
				for each (memberNode in memberNodes) 
				{
					if (!result[memberNode])
						result[memberNode] = currentType;
				}
			}
			
			// TODO: Do we need to lookup ancestors or just go only w/interfaces?
			typeNodes = type.descriptor.factory.extendsClass.@type;
			typeNodesLen = typeNodes.length();
			
			for (i = typeNodesLen - 1; i > -1 ; i--) 
			{
				currentType = describeClassType(String(typeNodes[i]));
				result[currentType.reference] = true;
				memberNodes = currentType.descriptor.factory.accessor.(@access == "readwrite").@name;
				
				for each (memberNode in memberNodes) 
				{
					if (!result[memberNode])
						result[memberNode] = currentType;
				}
				
				memberNodes = currentType.descriptor.factory.method.@name;
				
				for each (memberNode in memberNodes) 
				{
					if (!result[memberNode])
						result[memberNode] = currentType;
				}
			}
			
			memberNodes = type.descriptor.factory.accessor.(@access == "readwrite").@name;
			
			for each (memberNode in memberNodes) 
			{
				if (!result[memberNode])
					result[memberNode] = type;
			}
			
			memberNodes = type.descriptor.factory.method.@name;
			
			for each (memberNode in memberNodes) 
			{
				if (!result[memberNode])
					result[memberNode] = type;
			}
			
			type.members = result;
			
			return result;
		}
		
		public static function describeClassType(value:Object):ClassType
		{
			if (!value)
				throw new ArgumentError("Argument value:Object can not be null.");
			
			if (_cache[value])
				return _cache[value];
			
			var result:ClassType;
			const className:String = (value is String) ? 
				value as String : getQualifiedClassName(value);
			const type:Object = value as Class || ((value is String) ? 
				getDefinitionByName(value as String) : value.constructor);
			
			if (type)
			{
				result = new ClassType(className, type as Class, describeType(type));
				_cache[className] = result;
				_cache[type] = result;
				
				return result;
			}
			
			return null;
		}
		
		public static function instantiate(generator:Class, args:Array):Object
		{
			if (!generator)
				throw new ArgumentError("Argument generator:Class can not be null.");
			
			if (!args || !args.length)
				return new generator();
			
			switch (args.length)
			{
				case 1: return new generator(args[0]);
				case 2: return new generator(args[0], args[1]);
				case 3: return new generator(args[0], args[1], args[2]);
				case 4: return new generator(args[0], args[1], args[2], args[3]);
				case 5: return new generator(args[0], args[1], args[2], args[3], args[4]);
				case 6: return new generator(args[0], args[1], args[2], args[3], args[4], args[5]);
				case 7: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
				case 8: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
				case 9: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
				case 10: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
				case 11: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]);
				case 12: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]);
				case 13: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]);
				case 14: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]);
				case 15: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]);
				case 16: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]);
				case 17: return new generator(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]);
			}
			
			throw new Error("Limitation for constructor arguments is 17.");
		}
		
		// TODO: Improve.
		private static function processInterfaces(value:Object, type:ClassType):void
		{
			if (!type.interfaces)
			{
				type.interfaces = new Dictionary();
				const items:XMLList = type.descriptor.factory.implementsInterface.@type;
				
				for each (var item:XML in items) 
				{
					type.interfaces[getDefinitionByName(String(item))] = true;
				}
			}
		}
		
		private static function processAncestors(value:Object, type:ClassType):void
		{
			if (!type.ancestors)
			{
				type.ancestors = new Dictionary();
				const items:XMLList = type.descriptor.factory.extendsClass.@type;
				
				if (items.length() == 1)
					return;
				
				for each (var item:XML in items) 
				{
					type.ancestors[getDefinitionByName(String(item))] = true;
				}
			}
		}
	}
}